package notify

import (
	"context"
	"fmt"
	"log"
	"sync"
	"time"
)

type Progress struct {
	State int `json:"state"` // 1 处理中，2 中止， 3 发送完成 4，发送失败
	FailReason string `json:"fail_reason"`
	info map[string]*ProgressInfo `json:"info"`
	syncQueue chan NeedSyncPrgInfo
	maxCap int
	mux sync.RWMutex
}


func NewProgress(ctx context.Context,maxCap int,keys ...string) *Progress {
	ch := make(chan NeedSyncPrgInfo,32)
	p := &Progress{
		State:      0,
		FailReason: "",
		info:       nil,
		syncQueue: ch,
		maxCap: maxCap,
		mux:sync.RWMutex{},
	}
	info := make(map[string]*ProgressInfo,len(keys))
	for _,key := range keys  {
		info[key] = NewProgressInfo(key,maxCap,ch)
	}
	go func() {
		<-ctx.Done()
		for _, pi :=range p.info {
			pi.Close()
		}
		close(ch)
	}()
	p.info = info
	return p
}
func (p *Progress) AddProgressKey(key string)  {
	p.mux.Lock()
	defer p.mux.Unlock()
	if _,ok := p.info[key];ok{
		return
	}
	p.info[key] = NewProgressInfo(key,p.maxCap,p.syncQueue)
}
func (p *Progress) SetProgressState(key string,info NeedSyncPrgInfo){
	p.mux.Lock()
	defer p.mux.Unlock()
	p.info[key].FailCt = info.FailCt
	p.info[key].SuccessCt = info.SuccessCt
	p.info[key].Key = info.Key
}
func (p *Progress) GetProgressInfo() map[string]NeedSyncPrgInfo{
	data := map[string]NeedSyncPrgInfo{}
	if p.info == nil {
		return data
	}
	p.mux.RLock()
	defer p.mux.RUnlock()
	for key,val := range p.info {
		tmp := NeedSyncPrgInfo{
			Key:         val.Key,
			SuccessCt:   val.SuccessCt,
			FailCt:      val.FailCt,
		}
		success := make([]string,len(val.SuccessInfo))
		copy(success,val.SuccessInfo)
		tmp.SuccessInfo = success
		fail := make([]string,len(val.FailInfo))
		copy(fail,val.FailInfo)
		tmp.FailInfo = fail
		data[key] = tmp
	}
	return data
}
func (p *Progress) Close(){
	for _, pi :=range p.info {
		pi.Close()
	}
	close(p.syncQueue)
}
func (p *Progress) SyncQueue()  <-chan NeedSyncPrgInfo {
	return p.syncQueue
}
func (p *Progress) IncrSuccess(key string,info []string) error {
	p.mux.RLock()
	pinfo,ok := p.info[key]
	p.mux.RUnlock()

	if !ok {
		return fmt.Errorf("key %s don't exist",key)
	}
	if len(info) < 1 {
		return nil
	}
	ok = pinfo.addSucessInfo(info)
	if !ok {
		return fmt.Errorf("key %s add success error, max cap(%d),need(%d)",key,p.maxCap,len(info))
	}
	return nil
}
func (p *Progress) IncrFail(key string,info []string) error {
	p.mux.RLock()
	pinfo,ok := p.info[key]
	p.mux.RUnlock()
	if !ok {
		return fmt.Errorf("key %s don't exist",key)
	}
	if len(info) < 1 {
		return nil
	}
	ok = pinfo.addFailInfo(info)
	if !ok {
		return fmt.Errorf("key %s add fail error, max cap(%d),need(%d)",key,p.maxCap,len(info))
	}
	return nil
}
type ProgressInfo struct {
	NeedSyncPrgInfo
	mux sync.RWMutex
	syncQueue chan NeedSyncPrgInfo
	stop chan struct{}
}
type NeedSyncPrgInfo struct {
	Key string `json:"key"`
	SuccessCt int32 `json:"success_ct"`
	SuccessInfo []string `json:"success_info"`
	FailCt int32 `json:"fail_ct"`
	FailInfo []string `json:"fail_info"`
}
func NewProgressInfo (key string,maxCap int,ch chan NeedSyncPrgInfo) *ProgressInfo {
	pi :=&ProgressInfo{
		NeedSyncPrgInfo:NeedSyncPrgInfo{
			Key:         key,
			SuccessCt:   0,
			SuccessInfo: make([]string,0,maxCap),
			FailCt:      0,
			FailInfo:    make([]string,0,maxCap),
		},

		mux: sync.RWMutex{},
		syncQueue: ch,
		stop:make(chan struct{},0),
	}
	go func() {
		ticker := time.NewTicker(time.Microsecond*200)
		for  {
			select {
			case <-pi.stop:
				ticker.Stop()
				return
			case <-ticker.C:
				pi.mux.Lock()
				pi.sync()
				pi.mux.Unlock()
			}
		}
	}()
	return pi
}

func (pi *ProgressInfo) Close()  {
	close(pi.stop)
	pi.mux.Lock()
	pi.sync()
	pi.mux.Unlock()
}
func (pi *ProgressInfo) addSucessInfo (info []string) bool {
	if cap(pi.SuccessInfo) < len(info){
		return false
	}
	pi.mux.Lock()
	defer pi.mux.Unlock()
	if (cap(pi.SuccessInfo) - len(pi.SuccessInfo))<len(info) {
		pi.sync()
	}
	pi.SuccessCt += int32(len(info))
	pi.SuccessInfo = append(pi.SuccessInfo,info...)
	return true
}
func (pi *ProgressInfo) sync()  {
	if len(pi.SuccessInfo)<1 && len(pi.FailInfo)<1 {
		return
	}
	success := make([]string,len(pi.SuccessInfo))
	copy(success,pi.SuccessInfo)
	fail := make([]string,len(pi.FailInfo))
	copy(fail,pi.FailInfo)
	p := NeedSyncPrgInfo {
		Key:         pi.Key,
		SuccessCt:   pi.SuccessCt,
		SuccessInfo: success,
		FailCt:      pi.FailCt,
		FailInfo:    fail,
	}
	pi.syncQueue<- p
	pi.reset()
}
func (pi *ProgressInfo) addFailInfo(info []string) bool {
	if cap(pi.FailInfo) < len(info){
		return false
	}
	pi.mux.Lock()
	defer pi.mux.Unlock()
	if (cap(pi.FailInfo) - len(pi.FailInfo))<len(info) {
		pi.sync()
	}
	pi.FailCt += int32(len(info))
	pi.FailInfo = append(pi.FailInfo,info...)
	return true
}
func (pi *ProgressInfo) reset()  {
	pi.SuccessInfo = pi.SuccessInfo[:0]
	pi.FailInfo = pi.FailInfo[:0]
}

type SyncInfo struct {
	State int `json:"state"` // 1 处理中，2 中止， 3 发送完成 4，发送失败
	FailReason string `json:"fail_reason"`
	Infos []NeedSyncPrgInfo

}
type SyncProgress func(infos SyncInfo)

func SyncPrgToMQ(topic string) SyncProgress {
	return func(infos SyncInfo) {
		log.Printf("topic(%s),infos(%+v)",topic,infos)
	}
}